package com.googlecode.ascrblr.api.scrobbler;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.farng.mp3.MP3File;
import org.farng.mp3.TagException;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.Header;

import com.googlecode.ascrblr.api.scrobbler.TrackInfo;
import com.googlecode.ascrblr.api.scrobbler.TrackInfoList;
import com.googlecode.ascrblr.api.scrobbler.TrackInfo.SourceType;


/**
 * A factory for creating TrackInfo objects.
 */
public class TrackInfoFactory {

	/**
	 * Instantiates a new track info factory.
	 */
	private TrackInfoFactory() {
		super();
		// Uh, cant touch this ...
	}

	/** 
	 * The Constant instance. 
	 */
	private static final TrackInfoFactory instance = new TrackInfoFactory();

	/** 
	 * The Constant FILTER_DIR. 
	 */
	public static final FileFilter FILTER_DIR = new FileFilter() {
		
		/* (non-Javadoc)
		 * @see java.io.FileFilter#accept(java.io.File)
		 */
		public boolean accept(File pathname) {
			return pathname != null && pathname.exists()
					&& pathname.isDirectory() && pathname.canRead();
		}
	};

	/** 
	 * The Constant FILTER_MP3. 
	 */
	public static final FileFilter FILTER_MP3 = new FileFilter() {
		
		/* (non-Javadoc)
		 * @see java.io.FileFilter#accept(java.io.File)
		 */
		public boolean accept(File pathname) {
			return pathname != null && pathname.exists() && pathname.isFile()
					&& pathname.canRead()
					&& pathname.getName().endsWith(".mp3");
		}
	};
	
	/** 
	 * The Constant FILTER_COMBINED. 
	 */
	public static final FileFilter FILTER_COMBINED = new FileFilter() {
		
		/* (non-Javadoc)
		 * @see java.io.FileFilter#accept(java.io.File)
		 */
		public boolean accept(File pathname) {
			return pathname != null && (FILTER_DIR.accept(pathname) || FILTER_MP3.accept(pathname));
		}
	};
	
	
	/**
	 * Gets the files.
	 * 
	 * @param dir the dir
	 * @param list the list
	 * 
	 * @return the files
	 */
	public List<File> getFiles(File dir, List<File> list) {
		
		// if list is  not set yet, set new
		if (list == null) {
			list = new ArrayList<File>();
		}
		
		// if dir is null too, ... 
		if (dir == null || !dir.canRead()) {
			return list;
		}
		
		// if dir is just a file, try to add it:
		if (dir.isFile()) {
			list.add(dir);
			return list;
		}
		
		// get all subdirs
		List<File> subdirectories = new ArrayList<File>(Arrays.asList(dir.listFiles(FILTER_DIR)));
		
		// get all mp3 files
		List<File> filesInDir = new ArrayList<File>(Arrays.asList(dir.listFiles(FILTER_MP3)));
		
		// add all mp3-Files to list
		list.addAll(filesInDir);
		
		if (!subdirectories.isEmpty() ) {
			for (Iterator<File> it = subdirectories.iterator(); it.hasNext();) {
				getFiles(it.next(), list);
			}
		}
		
		return list;
	}
	
	
	/**
	 * Gets the mp3 files.
	 * 
	 * @param files the files
	 * 
	 * @return the mp3 files
	 */
	public static List<MP3File> getMp3Files(List<File> files) {
		
		List<MP3File> mp3files  = new ArrayList<MP3File>();
		
		for (File file : files) {
			if (!FILTER_MP3.accept(file)) {
				continue;
			}
			
			try {
				MP3File mp3File = new MP3File(file);
				mp3files.add(mp3File);
			} catch (Exception e) {
				// TODO: log
				continue;
			} 
		}
		
		return mp3files;
	}
	
	
	
	

	/**
	 * Creates a new TrackInfo object.
	 * 
	 * @param args the args
	 * 
	 * @return the track info list
	 */
	public TrackInfoList createListFromArgs(List<String> args) {
		TrackInfoList list = new TrackInfoList();

		if (args == null || args.size() == 0) {
			return list;
		}
		
		
		List<File> files =  new ArrayList<File>();
		
		
		// handle every arg as file/dir
		for (Iterator<String> it = args.iterator(); it.hasNext();) {
			String arg = it.next();
			
			
			File file = new File(arg);
			
			// if the file is neither mp3 file nor folder, cont.
			if (!FILTER_COMBINED.accept(file)) {
				continue;
			}
			
			files = getFiles(file, files);
			
		}
		
		
		List<MP3File> mp3List = getMp3Files(files);
		
		
		for (Iterator<MP3File> it = mp3List.iterator(); it.hasNext();) {
			try {
				TrackInfo trackInfo = create(it.next());
				list.add(trackInfo);
			} catch (TagException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				continue;
			}
		}
		
		return list;
	}
	
	
	
	
	
	

	/**
	 * Create.
	 * 
	 * @param mp3File the mp3 file
	 * 
	 * @return the track info
	 * 
	 * @throws TagException the tag exception
	 */
	public TrackInfo create(MP3File mp3File) throws TagException {
		if (mp3File == null
				|| !(mp3File.hasID3v2Tag() || mp3File.hasID3v1Tag())) {
			throw new TagException("MP3-File is null or has no Tags");
		}

		try {
			String artist = getArtist(mp3File);

			String track = getTrack(mp3File);
			String album = getAlbum(mp3File);
			int length = 31;
			length = getLengthInSeconds(mp3File);
			long startTime = System.currentTimeMillis() - (length + 5) * 1000;
			SourceType source = SourceType.P;

			int trackNo = getTrackNumber(mp3File);

			TrackInfo trackInfo = new TrackInfo(artist, track, startTime,
					length, source);

			trackInfo.setAlbum(album);

			// trackInfo.setMbTrackId(mbTrackId);
			// trackInfo.setRating(RatingType.)
			trackInfo.setSource(SourceType.P);
			trackInfo.setTrackNo(""+trackNo);

			return trackInfo;
		} catch (RuntimeException e) {
			throw e;
		}
	}
	
	
	/**
	 * Create.
	 * 
	 * @param mp3Files the mp3 files
	 * 
	 * @return the track info list
	 */
	public TrackInfoList create(List<MP3File> mp3Files)  {
		TrackInfoList trackInfoList = new TrackInfoList();
		
		for (MP3File mp3File : mp3Files) {
			try {
				TrackInfo trackInfo = create(mp3File);
				trackInfoList.add(trackInfo);
			} catch (TagException e) {
				continue;
			}
		}
		
		return trackInfoList;
	}
	
	

	/**
	 * Gets the artist.
	 * 
	 * @param mp3File the mp3 file
	 * 
	 * @return the artist
	 */
	private String getArtist(MP3File mp3File) {
		String artist = null;

		if (mp3File.hasID3v2Tag()) {
			artist = mp3File.getID3v2Tag().getLeadArtist();
		}
		if (isEmpty(artist) && mp3File.hasID3v1Tag()) {
			artist = mp3File.getID3v1Tag().getArtist();
		}

		if (isEmpty(artist)) {
			throw new IllegalStateException("No artist found for file: "
					+ mp3File);
		}

		return artist;
	}

	/**
	 * Gets the track.
	 * 
	 * @param mp3File the mp3 file
	 * 
	 * @return the track
	 */
	private String getTrack(MP3File mp3File) {
		String track = null;

		if (mp3File.hasID3v2Tag()) {
			track = mp3File.getID3v2Tag().getSongTitle();
		}
		if (isEmpty(track) && mp3File.hasID3v1Tag()) {
			track = mp3File.getID3v1Tag().getTitle();
		}

		if (isEmpty(track)) {
			throw new IllegalStateException("No track name found for file: "
					+ mp3File);
		}

		return track;
	}

	/**
	 * Gets the album.
	 * 
	 * @param mp3File the mp3 file
	 * 
	 * @return the album
	 */
	private String getAlbum(MP3File mp3File) {
		String album = null;

		if (mp3File.hasID3v2Tag()) {
			album = mp3File.getID3v2Tag().getAlbumTitle();
		}
		if (isEmpty(album) && mp3File.hasID3v1Tag()) {
			album = mp3File.getID3v1Tag().getAlbumTitle();
		}

		if (isEmpty(album)) {
			throw new IllegalStateException("No album name found for file: "
					+ mp3File);
		}

		return album;
	}

	/**
	 * Gets the track number.
	 * 
	 * @param mp3File the mp3 file
	 * 
	 * @return the track number
	 */
	private Integer getTrackNumber(MP3File mp3File) {
		Integer trackNumber = 1;

		if (mp3File.hasID3v2Tag()) {
			trackNumber = Integer.valueOf(mp3File.getID3v2Tag()
					.getTrackNumberOnAlbum());
		}
		if (trackNumber <= 0 && mp3File.hasID3v1Tag()) {
			trackNumber = Integer.valueOf(mp3File.getID3v1Tag()
					.getTrackNumberOnAlbum());
		}

		if (trackNumber <= 0) {
			trackNumber = 1;
		}

		return trackNumber;
	}

	/**
	 * Gets the length in seconds.
	 * 
	 * @param mp3File the mp3 file
	 * 
	 * @return The duration in seconds of this mp3 file.
	 */
	private Integer getLengthInSeconds(MP3File mp3File) {

		Double send = -1.0;

		try {
			Bitstream m_bitstream = new Bitstream(new FileInputStream(mp3File
					.getMp3file()));
			Header m_header = m_bitstream.readFrame();

			int mediaLength = (int) mp3File.getMp3file().length();
			int nTotalMS = Math.round(m_header.total_ms(mediaLength));
			m_bitstream.closeFrame();
			m_bitstream.close();

			send = (double) nTotalMS / 1000.0;
		} catch (Exception e) {

		}

		return send.intValue();

	}

	/**
	 * Gets the tracks.
	 * 
	 * @param files the files
	 * 
	 * @return the tracks
	 */
	private TrackInfoList getTracks(List<File> files) {
		TrackInfoList list = new TrackInfoList();

		TrackInfo track = null;
		for (Iterator<File> it = files.iterator(); it.hasNext();) {
			try {
				MP3File mp3 = new MP3File(it.next());
				track = create(mp3);

			} catch (Exception e) {
				continue;
			}
			//list.add(track);
		}

		return list;
	}

	/**
	 * Creates a new TrackInfo object.
	 * 
	 * @param directory the directory
	 * 
	 * @return the track info list
	 */
	public TrackInfoList createList(String directory) {
		return createList(new File(directory));
	}

	/**
	 * Creates a new TrackInfo object.
	 * 
	 * @param directory the directory
	 * 
	 * @return the track info list
	 */
	public TrackInfoList createList(File directory) {
		if (directory == null || !directory.isDirectory()
				|| !directory.canRead() || directory.listFiles().length <= 0) {
			throw new IllegalArgumentException("Cannot read dir: " + directory);
		}

		ArrayList<File> files = new ArrayList<File>(Arrays.asList(directory
				.listFiles(FILTER_MP3)));

		return getTracks(files);
	}

	/**
	 * Gets the single instance of TrackInfoFactory.
	 * 
	 * @return single instance of TrackInfoFactory
	 */
	public static TrackInfoFactory getInstance() {
		return instance;
	}

	/**
	 * Checks if is empty.
	 * 
	 * @param s the s
	 * 
	 * @return true, if is empty
	 */
	private boolean isEmpty(String s) {
		return "".equals((s != null) ? s.trim() : "");
	}

}
